package org.fioma.general;

import java.util.Arrays;

public class Tools {

	public static final byte BYTEG = 2;
	public static final byte BYTEC = 1;
	public static final byte BYTET = 3;
	public static final byte BYTEA = 0;

	public static final short compare(char[] text, char[] word) {
		int diff = text.length - word.length;
		if (diff == 0) { // text and word have the same length
			for (short i = 0; i < text.length; i++) {
				if (text[i] != word[i]) {
					return i;
				}
			}
			return -1;
		} else { // the lengths are different
			short lim;
			if (diff < 0) { // text is smaller than word
				lim = (short) text.length;
			} else { // text is greater than word
				lim = (short) word.length;
			}
			for (short i = 0; i < lim; i++) {
				if (text[i] != word[i]) {
					return i;
				}
			}
			return lim;
		}

	}

	// -1 or the position of the mismatch
	public static final short compare(byte[] text, byte[] word) {
		int diff = text[0] - word[0];
		if (diff == 0) { // text and word have the same length
			for (short i = 0; i < text.length - 1; i++) {
				if (text[i + 1] != word[i + 1]) {
					short ret = (short) (4 * i + compare(text[i + 1], word[i + 1]));
					if (ret < text[0]) {
						return ret;
					} else {
						return -1;
					}
				}
			}
			return -1;
		} else { // the lengths are different
			short lim;
			short ret;
			if (diff < 0) { // text is smaller than word
				lim = (short) text.length;
				ret = text[0];
			} else { // text is greater than word
				lim = (short) word.length;
				ret = word[0];
			}
			for (short i = 1; i < lim; i++) {
				if (text[i] != word[i]) {
					short ret1 = (short) (4 * (i - 1) + compare(text[i], word[i]));
					if (ret1 < text[0] && ret1 < word[0]) {
						return ret1;
					} else {
						return ret;
					}
				}
			}
			return ret;
		}
	}

	public static final byte compare(byte b1, byte b2) {
		byte res = (byte) (b1 ^ b2);
		if ((res & 0x03) != 0) {
			return 0;
		} else if ((res & 0x0C) != 0) {
			return 1;
		} else if ((res & 0x30) != 0) {
			return 2;
		} else if ((res & 0xC0) != 0) {
			return 3;
		}
		return -1;
	}

	public static final char transformToChar(byte b) {
		switch (b) {
		case BYTEA:
			return 'A';
		case BYTET:
			return 'T';
		case BYTEC:
			return 'C';
		case BYTEG:
			return 'G';
		default:
			throw new RuntimeException("Invalid byte:" + b);
		}
	}

	public static final byte transformToByte(char c) {
		switch (c) {
		case 'A':
		case 'a':
			return BYTEA;
		case 'T':
		case 't':
			return BYTET;
		case 'C':
		case 'c':
			return BYTEC;
		case 'G':
		case 'g':
			return BYTEG;
		default:
			return BYTEA;
//			throw new RuntimeException("Invalid character:" + c);
		}
	}

	public static final byte transformToByteComplement(char c) {
		switch (c) {
		case 'A':
		case 'a':
			return BYTET;
		case 'T':
		case 't':
			return BYTEA;
		case 'C':
		case 'c':
			return BYTEG;
		case 'G':
		case 'g':
			return BYTEC;
		default:
			return BYTET;
//			throw new RuntimeException("Invalid character:" + c);
		}
	}

	public static final byte transformToByteComplement(char c1, char c2, char c3, char c4) {
		byte ret = transformToByteComplement(c1);
		ret |= shiftLeft(transformToByteComplement(c2), 2);
		ret |= shiftLeft(transformToByteComplement(c3), 4);
		ret |= shiftLeft(transformToByteComplement(c4), 6);
		return ret;
	}

	public static final byte transformToByteComplement(char c1, char c2, char c3) {
		byte ret = transformToByteComplement(c1);
		ret |= shiftLeft(transformToByteComplement(c2), 2);
		ret |= shiftLeft(transformToByteComplement(c3), 4);
		return ret;
	}

	// return byte = c4 & c3 & c2 & c1
	public static final byte transformToByteComplement(char c1, char c2) {
		byte ret = transformToByteComplement(c1);
		ret |= shiftLeft(transformToByteComplement(c2), 2);
		return ret;
	}

	// return byte = c4 & c3 & c2 & c1
	public static final char[] transformToChars(byte b) {
		char[] ret = new char[4];
		ret[0] = transformToChar((byte) (b & 0x03));
		ret[1] = transformToChar((byte) (shiftRight(b, 2) & 0x03));
		ret[2] = transformToChar((byte) (shiftRight(b, 4) & 0x03));
		ret[3] = transformToChar((byte) (shiftRight(b, 6) & 0x03));
		return ret;
	}

	public static final byte transformToByte(char c1, char c2, char c3, char c4) {
		byte ret = transformToByte(c1);
		ret |= shiftLeft(transformToByte(c2), 2);
		ret |= shiftLeft(transformToByte(c3), 4);
		ret |= shiftLeft(transformToByte(c4), 6);
		return ret;
	}

	public static final byte transformToByte(char c1, char c2, char c3) {
		byte ret = transformToByte(c1);
		ret |= shiftLeft(transformToByte(c2), 2);
		ret |= shiftLeft(transformToByte(c3), 4);
		return ret;
	}

	// return byte = c4 & c3 & c2 & c1
	public static final byte transformToByte(char c1, char c2) {
		byte ret = transformToByte(c1);
		ret |= shiftLeft(transformToByte(c2), 2);
		return ret;
	}

	public static final char[] transformToChars(byte[] b) {
		byte length = b[0];
		char[] ret = new char[length];

		char[] r;
		int i;
		for (i = 0; (i * 4 + 3) < length; i++) {
			r = transformToChars(b[i + 1]);
			for (int j = 0; j < 4; j++) {
				ret[i * 4 + j] = r[j];
			}
		}

		// Analyze the remainder chars in an incomplete byte
		int mod = length % 4;
		if (mod > 0) {
			r = transformToChars(b[i + 1]);
			for (int j = 0; j < mod; j++) {
				ret[i * 4 + j] = r[j];
			}
		}
		return ret;
	}

	public static final byte[] transformToBytes(char[] word) {
		int size = ceil4(word.length);
		size++; // byte to store the length of the sequence
		byte[] ret = new byte[size];
		ret[0] = (byte) word.length;

		int i;
		for (i = 0; (i * 4 + 3) < word.length; i++) {
			ret[i + 1] = transformToByte(word[i * 4], word[i * 4 + 1], word[i * 4 + 2], word[i * 4 + 3]);
		}

		// Analyze the remainder chars in an incomplete byte
		byte mod = (byte) (word.length % 4);

		switch (mod) {
		case 1:
			ret[i + 1] = transformToByte(word[i * 4]);
			break;
		case 2:
			ret[i + 1] = transformToByte(word[i * 4], word[i * 4 + 1]);
			break;
		case 3:
			ret[i + 1] = transformToByte(word[i * 4], word[i * 4 + 1], word[i * 4 + 2]);
			break;
		}
		return ret;
	}

	public static final char[] getCharsInverted(String seqInTheLine) {
		int length = seqInTheLine.length();
		char[] res = new char[length];

		for (int i = 0; i < length; i++) {
			res[length - 1 - i] = seqInTheLine.charAt(i);
		}
		return res;
	}

	public static final char[] getCharsInverted(char[] sequence) {
		int length = sequence.length;
		char[] res = new char[length];

		for (int i = 0; i < length; i++) {
			res[length - 1 - i] = sequence[i];
		}
		return res;
	}

	public static final byte[] getBytesInverted(String sequence) {
		return transformToBytes(getCharsInverted(sequence));
	}

	public static final byte[] getBytesInverted(char[] sequence) {
		return transformToBytes(getCharsInverted(sequence));
	}

	public static final byte[] getBytesInverted(byte[] word) {
		byte[] ret = new byte[word.length];
		ret[0] = word[0];
		for (int i = 1; i < word.length; i++) {
			ret[i] = (byte) (shiftLeft((byte) (word[i] & 0x03), 6) | shiftLeft((byte) (word[i] & 0x0C), 2) | shiftRight((byte) (word[i] & 0x30), 2) | shiftRight(
					(byte) (word[i] & 0xC0), 6));
		}
		return ret;
	}

	public static final byte[] getBytesComplementar(String sequence) {
		return getBytesComplementar(sequence.toCharArray());
	}

	public static final byte[] getBytesComplementar(byte[] word) {
		byte[] ret = new byte[word.length];
		ret[0] = word[0];
		for (int i = 1; i < word.length; i++) {
			ret[i] = (byte) (word[i] ^ 0xFF);
		}
		return ret;
	}

	public static final byte[] getBytesComplementar(char[] word) {
		int size = ceil4(word.length);
		size++; // byte to store the length of the sequence
		byte[] ret = new byte[size];
		ret[0] = (byte) word.length;

		int i;
		for (i = 0; (i * 4 + 3) < word.length; i++) {
			ret[i + 1] = transformToByteComplement(word[i * 4], word[i * 4 + 1], word[i * 4 + 2], word[i * 4 + 3]);
		}

		// Analyze the remainder chars in an incomplete byte
		byte mod = (byte) (word.length % 4);

		switch (mod) {
		case 1:
			ret[i + 1] = transformToByteComplement(word[i * 4]);
			break;
		case 2:
			ret[i + 1] = transformToByteComplement(word[i * 4], word[i * 4 + 1]);
			break;
		case 3:
			ret[i + 1] = transformToByteComplement(word[i * 4], word[i * 4 + 1], word[i * 4 + 2]);
			break;
		}

		return ret;
	}

	// pos from 0 to word[0] (length of the word)
	// new words will be ret=[0..pos-1] and ret[2]=[pos..length-1]
	public static final byte[][] splitWord(byte[] word, short pos) {
		if (pos > word[0]) {
			pos = word[0];
		}
		int size = ceil4((byte) pos) + 1;
		byte[] ret1 = Arrays.copyOf(word, size);
		ret1[0] = (byte) pos;

		int length2 = (word[0] - pos);
		int size2 = (ceil4(length2) + 1);
		byte[] ret2 = new byte[size2];
		ret2[0] = (byte) length2;

		int mod = pos % 4;
		for (int i = 1; i < size2; i++) {
			byte b;
			if (mod == 0) {
				b = word[size + i - 1];
			} else {
				b = shiftRight(word[size + i - 2], 2 * mod);
				if (size + i - 1 < word.length) {
					b |= shiftLeft(word[size + i - 1], 2 * (4 - mod));
				}
			}
			ret2[i] = b;

		}

		byte[][] ret = { ret1, ret2 };
		return ret;
	}

	public static final byte[] joinWords(byte[] word1, byte[] word2) {
		int length = (word1[0] + word2[0]);
		byte[] ret = new byte[ceil4(length) + 1];
		ret[0] = (byte) length;

		for (int i = 1; i < word1.length; i++) {
			ret[i] = word1[i];
		}

		int mod1 = word1[0] % 4;
		if (mod1 == 0) {
			for (int i = 1; i < word2.length; i++) {
				ret[word1.length + i - 1] = word2[i];
			}
		} else {
			// shifts are only to lets zero in the left
			byte lastB = shiftRight(shiftLeft(word1[word1.length - 1], 2 * (4 - mod1)), 2 * (4 - mod1));
			for (int i = 1; i < word2.length; i++) {
				ret[word1.length + i - 2] = (byte) ((shiftLeft(word2[i], 2 * mod1) | lastB) & 0xff);
				lastB = shiftRight(word2[i], 2 * (4 - mod1));
			}
			int mod2 = word2[0] % 4;
			// if there are remainder chars...
			if (mod2 == 0 || (mod1 + mod2 > 4)) {
				ret[word1.length + word2.length - 2] = lastB;
			}
		}
		return ret;
	}

	// return word[pos..pos+length-1]
	public static final byte[] substring(byte[] word, int pos) {
		if (word == null || word.length == 0 || word[0] == 0 || pos >= word[0]) {
			return new byte[1];
		}
		return substring(word, pos, word[0] - pos);
	}

	// return word[pos..pos+length-1]
	public static final byte[] substring(byte[] word, int pos, int length) {
		if (word == null || word.length == 0 || word[0] == 0 || pos >= word[0]) {
			return new byte[1];
		}
		if (pos + length > word[0]) {
			length = word[0] - pos;
		}
		int size = ceil4(length) + 1;
		byte[] ret = new byte[size];
		ret[0] = (byte) length;

		int mod = pos % 4;
		int startByteInTheWord = ceil4(pos + 1);
		for (int i = 1; i < size; i++) {

			if (mod == 0) {
				ret[i] = word[startByteInTheWord + i - 1];
			} else {
				byte b;
				b = shiftRight(word[startByteInTheWord + i - 1], 2 * mod);
				if (startByteInTheWord + i < word.length) {
					b |= shiftLeft(word[startByteInTheWord + i], 2 * (4 - mod));
				}
				ret[i] = b;
			}
		}

		return ret;
	}

	public static final byte getByte(byte[] word, int pos) {
		return shiftRight(shiftLeft(word[ceil4(pos + 1)], 2 * (3 - (pos % 4))), 6);
	}

	public static final byte shiftRight(byte b, int times) {
		return (byte) (((b & 0xff) >>> times) & 0xff);
	}

	public static final byte shiftLeft(byte b, int times) {
		return (byte) (((b & 0xff) << times) & 0xff);
	}

	public static int getMismatch(byte[] word1, int pos1, int length1, byte[] word2, int pos2, int length2) {
		int i;
		for (i = 0; i < length1 && i < length2; i++) {
			if (getByte(word1, pos1 + i) != getByte(word2, pos2 + i)) {
				return i;
			}
		}
		if (length1 == length2) {
			return -1;
		}
		return i;
	}

	public static int getMismatchesQtty(byte[] word1, int pos1, int length1, byte[] word2, int pos2, int length2) {
		int count = 0;
		for (int i = 0; i < length1 && i < length2; i++) {
			if (getByte(word1, pos1 + i) != getByte(word2, pos2 + i)) {
				count++;
			}
		}
		return count;
	}

//	public static final <I extends SimpleInternalNodeBytes<I, F>, F extends SimpleLeafBytes<I, F>> F addNodeBytes(Node<byte[], I, F> nodeThis, byte pos,
//			byte[] w) {
//		F leaf = null;
//		if (w.length > 1 && w[0] > 0 && pos >= 0 && pos < nodeThis.getLengthLabel()) {
//			if (pos == 0) {
//				// create a new child in the parent
//				byte bThis = (byte) (nodeThis.getLabel()[1] & 0x03);
//				byte bLeaf = (byte) (w[1] & 0x03);
//				if (((bThis) ^ (bLeaf)) == 0) {
//					System.out.println("2 children start with the same label.");
//				}
//
//				leaf = nodeThis.getParent().createLeaf(w);
//				leaf.getParent().setChild(leaf, bLeaf);
//			} else {
//				byte[][] splittedLabels = Tools.splitWord(nodeThis.getLabel(), pos);
//
//				I newNode = nodeThis.getParent().createInternalNode(splittedLabels[0]);
//				leaf = newNode.createLeaf(w);
//
//				nodeThis.setLabel(splittedLabels[1]);
//				nodeThis.setParent(newNode);
//
//				byte bThis = (byte) (splittedLabels[1][1] & 0x03);
//				byte bLeaf = (byte) (w[1] & 0x03);
//				byte bNewNode = (byte) (splittedLabels[0][1] & 0x03);
//
//				if (((bThis) ^ (bLeaf)) == 0) {
//					System.out.println("2 children start with the same label.");
//				}
//
//				newNode.setChild(nodeThis, bThis);
//				newNode.setChild(leaf, bLeaf);
//				newNode.getParent().setChild(newNode, bNewNode);
//			}
//		} else {
//			System.out.println("Position " + pos + " greater or equal than label length " + nodeThis.getLengthLabel());
//		}
//		return leaf;
//	}

	public static int ceil4(int b) {
		int ret = (b / 4);
		if (b % 4 != 0) {
			ret++;
		}
		return ret;
	}

}
